﻿using System;
using System.Collections.Generic;
using UnityEngine;

namespace AI.Graph {


    public class AIController : ScriptableObject {

#region Fields

        /// <summary>
        /// The rendered offset of this controller.
        /// </summary>
        [SerializeField, HideInInspector]
        private Vector2 m_offset;

        /// <summary>
        /// All Nodes that are in this controller.
        /// </summary>
        [SerializeField, HideInInspector]
        private List<Node> m_nodes = new List<Node> ();

        /// <summary>
        /// The entry node.
        /// </summary>
        [SerializeField, HideInInspector]
        private EntryState m_entryState;

        /// <summary>
        /// The any node.
        /// </summary>
        [SerializeField, HideInInspector]
        private AnyState m_anyState;

        /// <summary>
        /// The exposed parameters that are used to reference objects. 
        /// </summary>
        [SerializeField, HideInInspector]
        private ExposedParameter[] m_exposedParameters = new ExposedParameter[0];

        /// <summary>
        /// The node parameters that are used to reference node values.
        /// </summary>
        [SerializeField, HideInInspector]
        private NodeParameter[] m_nodeParameters = new NodeParameter[0];

#endregion


#region Properties

        [SerializeField]
        internal bool isDirty;

        public bool isCopy { get; private set; }

        /// <summary>
        /// The rendered offset of this controller.
        /// </summary>
        internal Vector2 offset {
            get { return m_offset; }
            set { m_offset = value; }
        }

        /// <summary>
        /// All nodes that are in this controller.
        /// </summary>
        public Node[] nodes {
            get { return m_nodes.ToArray (); }
        }

        /// <summary>
        /// The entry node.
        /// </summary>
        public EntryState entryState {
            get { return m_entryState; }
            internal set { m_entryState = value; }
        }

        /// <summary>
        /// The any node.
        /// </summary>
        public AnyState anyState {
            get { return m_anyState; }
            internal set { m_anyState = value; }
        }

        public ExposedParameter[] exposedParameters {
            get { return m_exposedParameters; }
        }

        public NodeParameter[] nodeParameters {
            get { return m_nodeParameters; }
        }

        public event Action<Node> onNodeAdded;
        public event Action<Node> onNodeRemoved;

#endregion


        /// <summary>
        /// Add the given node to this controller.
        /// Don't call it directly.
        /// </summary>
        /// <param name="node">The node to add.</param>
        internal void AddNode (Node node) {
            m_nodes.Add (node);
            onNodeAdded?.Invoke (node);
        }


        /// <summary>
        /// Remove the given node from this controller.
        /// Don't call it directly.
        /// </summary>
        /// <param name="node">The node to remove.</param>
        internal void RemoveNode (Node node) {
            m_nodes.Remove (node);
            onNodeRemoved?.Invoke (node);
        }


        string GetUniqueParameterName<T> (T[] parameterList, string parameterName, T oldParameter = null) where T : Parameter {
            string uniqueParameterName = parameterName;
            bool uniqueNameFound;

            int i = 0;
            do {
                uniqueNameFound = true;

                foreach (T parameter in parameterList) {
                    if (oldParameter == parameter) continue;

                    if (parameter.name == uniqueParameterName) {
                        uniqueNameFound = false;
                        uniqueParameterName = parameterName + " (" + i + ")";
                        i++;
                        break;
                    }
                }
            } while (!uniqueNameFound);

            return uniqueParameterName;
        }


        string GetUniqueExposedParameterName (string parameterName, ExposedParameter oldParameter = null) {
            return GetUniqueParameterName (m_exposedParameters, parameterName, oldParameter);
        }


        /// <summary>
        /// Creates an unique exposed parameter and returns his index.
        /// </summary>
        internal int CreateUniqueExposedParameter (out ExposedParameter newParameter) {
            string uniqueParameterName = GetUniqueExposedParameterName ("Parameter");
            ExposedParameter parameter = CreateInstance<ExposedParameter> ();
            parameter.name = uniqueParameterName;

            newParameter = parameter;
            ArrayUtility.Add (ref m_exposedParameters, newParameter);
            return m_exposedParameters.Length - 1;
        }


        /// <summary>
        /// Removes the given exposed parameter.
        /// </summary>
        internal void RemoveExposedParameter (ExposedParameter parameter) {
            ArrayUtility.Remove (ref m_exposedParameters, parameter);
        }


        /// <summary>
        /// Change the index of the given exposed parameter.
        /// </summary>
        internal void ChangeExposedParameterIndex (int newIndex, ExposedParameter exposedParameter) {
            ArrayUtility.Remove (ref m_exposedParameters, exposedParameter);
            ArrayUtility.Insert (ref m_exposedParameters, newIndex, exposedParameter);
        }


        /// <summary>
        /// Change the name of the given exposed parameter.
        /// </summary>
        internal void ChangeExposedParameterName (string newName, ExposedParameter parameter) {
            string uniqueParameterName = GetUniqueExposedParameterName (newName, parameter);
            parameter.name = uniqueParameterName;
        }


        /// <summary>
        /// Returns true if the exposed parameter exists in this controller.
        /// </summary>
        internal bool ContainsExposedParameter (ExposedParameter parameter) {
            return ArrayUtility.Contains (m_exposedParameters, parameter);
        }


        string GetUniqueNodeParameterName (string parameterName, NodeParameter oldParameter = null) {
            return GetUniqueParameterName (m_nodeParameters, parameterName, oldParameter);
        }


        /// <summary>
        /// Creates an unique node parameter and returns his index.
        /// </summary>
        internal int CreateUniqueNodeParameter (out NodeParameter newParameter) {
            string uniqueParameterName = GetUniqueNodeParameterName ("Parameter");
            NodeParameter parameter = CreateInstance<NodeParameter> ();
            parameter.name = uniqueParameterName;

            newParameter = parameter;

            ArrayUtility.Add (ref m_nodeParameters, newParameter);
            return m_nodeParameters.Length - 1;
        }


        /// <summary>
        /// Removes the given node parameter.
        /// </summary>
        internal void RemoveNodeParameter (NodeParameter parameter) {
            ArrayUtility.Remove (ref m_nodeParameters, parameter);
        }


        /// <summary>
        /// Change the index of the given node parameter.
        /// </summary>
        internal void ChangeNodeParameterIndex (int newIndex, NodeParameter nodeParameter) {
            ArrayUtility.Remove (ref m_nodeParameters, nodeParameter);
            ArrayUtility.Insert (ref m_nodeParameters, newIndex, nodeParameter);
        }


        /// <summary>
        /// Change the name of the given node parameter.
        /// </summary>
        internal void ChangeNodeParameterName (string newName, NodeParameter parameter) {
            string uniqueParameterName = GetUniqueNodeParameterName (newName, parameter);
            parameter.name = uniqueParameterName;
                
        }


        /// <summary>
        /// Returns true if the node parameter exists in this controller.
        /// </summary>
        internal bool ContainsNodeParameter (NodeParameter parameter) {
            return ArrayUtility.Contains (m_nodeParameters, parameter);
        }


        internal void Copy (AIControllerSerializer serializer, AIController original) {
            isCopy = true;
            m_entryState = serializer.GetCopyOf (original.m_entryState);
            m_anyState = serializer.GetCopyOf (original.m_anyState);

            m_nodes = new List<Node> ();
            foreach (Node originalNode in original.m_nodes) {
                m_nodes.Add (serializer.GetCopyOf (originalNode));
            }
        }

    }


}